'''※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
File Name: mysendemail.py
Author: GID5564
Description: 邮件内容定制及发送邮件
Version: 1.0
Created Time: 01/05/23-09:00:47
※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※''
'''
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import os
import sys
import time
import random
import re
import mimetypes  
from email.mime.base import MIMEBase
from email.mime.audio import MIMEAudio 
from email.encoders import encode_base64
from email import encoders
#用来构造邮件内容的库
from email.mime.image import MIMEImage  
#邮件头
from email.header import Header
#邮件文本
from email.mime.text import MIMEText
#邮件体
from email.mime.multipart import MIMEMultipart
# 发送邮件
import smtplib  
from smtplib import  SMTP_SSL
from datetime import datetime
import sched



# 判断网络是否联通,成功返回0，不成功返回1# linux中ping命令不会自动停止，需要加入参数 -c 4，表示在发送指定数目的包后停止。
def isLink():    
    return os.system('ping -c 4 www.baidu.com')    
            
#返回一串随机数
def get_random_num():
    num=str(int(time.time())) + str(random.randint(100, 999))
    return num
#检查邮箱地址是否合理
def iseMail(addr):
    rule=r"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"
    res=re.fullmatch(rule,addr,re.I)
    if res == None:
        return None
    else:
        return res.group()
        

def convert_date_string(date_str):
    """
    将给定的日期字符串转换为 '%Y/%m/%d:%H:%M:%S' 格式。
    
    参数：
    date_str (str): 输入的日期字符串，可以是 "2024/1/2"、"2024/1/2 12:45" 或 "2024/1/2 :12:45:22" 格式。
    
    返回：
    str: 转换后的日期字符串，格式为 '%Y/%m/%d:%H:%M:%S'。
    """
    # 定义可能的日期和时间格式列表
    formats = [
        '%Y/%m/%d %H:%M:%S',
        '%Y/%m/%d %H:%M',
        '%Y/%m/%d %H',
        '%Y/%m/%d'
    ]
    
    # 尝试解析日期和时间
    for fmt in formats:
        try:
            dt = datetime.strptime(date_str, fmt)
            break
        except ValueError:
            continue
    else:
        print("无效日期格式！")
        return None
    
    # 格式化日期和时间
    return dt.strftime('%Y/%m/%d %H:%M:%S')


class BaseMail():
    def __init__(self):
        pass
    def set_attach(self,title,sender,receivers):
        #检查邮箱格式
        assert iseMail(sender)!=None,f"{sender}邮箱格式不正确!"
        em=receivers.split(",")
        for i in range(len(em)):
            assert iseMail(em[i])!=None ,f"{em[i]}邮箱格式不正确"
    
        #内容类型
        msg = MIMEMultipart('mixed')
        # 标题
        msg['Subject'] = Header(title)
        msg['From'] = sender  # 发送方邮箱
        msg['To'] = receivers  # 多个接收方邮箱
    
        return msg
    def send_email(self,host,port,pwd,msg):
        try:
            smtp=SMTP_SSL(host,port)
            smtp.login(msg['From'], pwd)
            smtp.sendmail(msg['From'], msg['To'].split(','), msg.as_string())
        except smtplib.SMTPException as e:
            print(f"邮件发送失败: {e}")
        else:
            time.sleep(2)
            print("邮件发送成功!")
            smtp.quit()
            
            
    #添加附件
    def add_enclosur(self, msg, enclosurs):
        if not enclosurs:
            return
        enc = enclosurs.split(',')
        for file_path in enc:
            if os.path.isfile(file_path):
                self.__getAttachment(msg, file_path)

            
    #添加邮件正文[html文件或文本]
    #type:  plain，纯文本
    #       html，html格式
    def add_body(self,msg,content,type):
        #html
        #文案(text,image,link)
        self.__set_content(msg,content,type)
            
            
    #添加邮件正文-样式文本
    def add_text(self,msg,text,**kwds):
        #设置字体样式
        for key in kwds:
            if key not in ['color', 'size','italic','bold','family','align','decoration','bgcolor','line_H','shadow']:
                raise KeyError('不支持的关键字参数：%s'%key)
        #设默认值
        color = kwds.get('color', "#000000")
        size = kwds.get('size', 24)
        # italic
        # oblique
        # normal
        italic = kwds.get('italic', "normal")
        #italic="italic" if italic_ else "normal"
        # normal
        # bold
        # bolder
        bold = kwds.get('bold', "normal")
        #bold="bold" if bold_ else "normal"
        family = kwds.get('family', "微软雅黑")
        # left
        # center
        # right
        align = kwds.get('align', "center")
        # none : 取消所有文本修饰 、
        # underline : 为文本添加下划线 、 
        # overline : 为文本添加上划线 、 
        # line-through : 为文本添加删除线
        decoration = kwds.get('decoration', "none")
        bgcolor = kwds.get('bgcolor',"#ffffff")
        #行高
        line_H=kwds.get('line_H',"normal")
        #制作滤镜阴影，使用逗号分隔设置多个阴影 text-shadow:h-shadow v-shadow blur color
        # h-shadow : 必需。水平阴影的位置。允许负值。
        # v-shadow : 必需。垂直阴影的位置。允许负值。
        # blur : 可选。模糊的距离。
        # color : 可选。阴影的颜色
        # "-2px 2px 2px #ff0000"
        shadow=kwds.get('shadow',"none")
    
        font_style=f"font-size:{size} px;font-color:{color};background-color:{bgcolor};font-weight:{bold};font-style:{italic};text-decoration:{decoration};text-align:{align};line-height:{line_H};text-shadow:{shadow};"
        code=f"<p style='text-align:center;'><font style='{font_style}'>{text}</font></p>"
        self.__set_content(msg,code,"html")
    
    #添加邮件正文-图片
    def add_image(self,msg,image,**kwds):
        #读取图片，并定义ID返回，在html代码中汝引用
        cid=set_email_image(msg,image)
        for key in kwds:
            if key not in ['wigth','height','border_style','border_color','border_width','align','valign','around','radius','alpha','shadow']:
                raise KeyError('不支持的关键字参数：%s'%key)
        #设默认值
        wigth = kwds.get('wigth', 300)
        height = kwds.get('height', 400)
        #边框样式: none无样式 solid实线 outset外凸 groove槽线 dotted点划线 dashed虚线 inset内凹 ridge脊线 double双线
        border_style = kwds.get('border_style', "none")
        border_color = kwds.get('border_color', "#ffffff")
        border_width = kwds.get('border_width', 0)
        #left center right
        align = kwds.get('align', "center")
        #baseline默认值 sub垂直对齐文本的下标 super垂直对齐文本的上标 top顶端对齐 middle中部对齐 bottom底部对齐 text-top顶端对齐 text-botom底端对齐
        valign = kwds.get('valign', "middle")
        #nnoe left right
        around = kwds.get('around', "none")
        #圆角12px
        radius=kwds.get('radius', 0)
        #透明[0.0-1.0]
        alpha=kwds.get('alpha', 1.0)
        #同上，box-shadow:rgba(0,0,0,0.2) 0 1px 5px 0px;
        shadow = kwds.get('shadow',"none")
    
        image_style_1=f"wigth:{wigth} px;height:{height} px;border-style:{border_style};border-color:{border_color};border-width:{border_width};float:{around};"
        image_style_2=f"opacity:{alpha};border-radius:{radius}px;box-shadow:{shadow};"
        p_style=f"text-align:{align};vertical-align:{valign};"
        #显示图片的代码
        code=f"<p style='{p_style}'><img src='cid:{cid}' style='{image_style_1}{image_style_2}' ></p>"
    
        #上传代码，显示图片
        self.__set_content(msg,code,"html")
        
    #添加邮件正文-链接
    def add_link(self,msg,content,url,width=300,height=400,flag=0):
        if flag==1:#图片
            cid=self.__set_image(msg,content)
            #显示图片的代码
            code=f"<p style='text-align:center;'><a href='{url}'><img src='cid:{cid}' width='{width} px' height='{height} px' ></a></p>"
        else:
            code=f"<p style='text-align:center;'><a href='{url}'>{content}</a></p>"
        self.__set_content(msg,code,"html")
            
            
    #设置文本或代码字符串
    def __set_content(self,msg,text_or_file,type):
        if os.path.isfile(text_or_file):
            try:
                with open(text_or_file,'rb') as f:
                    content=MIMEText(f.read(),type,'utf-8')
            except Exception as e:
                print(e)
                content=None
        else:
            content=MIMEText(text_or_file,type,'utf-8')
        if content is not None:
            msg.attach(content)
        
    #设置正文可添加的图片，如:#正文中引用的:<img src="cid:{CID}">
    def __set_image(self,msg,file):
        cid=get_random_num()
        try:
            with open(file, 'rb') as f:
                img = MIMEImage(f.read())
                #取文件名
                file_name=file.split('/')[-1]
                img.add_header('Content-Disposition','attachment',filename = ('gbk', '', file_name))
                #正文中引用的:<img src="cid:{CID}">
                img.add_header('Content-ID', cid)
        except Exception as e:
            print(e)
            img=None
        if img is not None:
            msg.attach(img)
        return cid
  
    def __getAttachment(self,msg,attachmentFilePath):  # 获取附件，参数：文件路径
        contentType, encoding = mimetypes.guess_type(attachmentFilePath)  # 根据 guess_type方法判断文件的类型和编码方式
        if contentType is None or encoding is not None:  # 如果根据文件的名字/后缀识别不出是什么文件类型
            contentType = 'application/octet-stream'   # 使用默认类型，usable for a MIME content-type header.
        mainType, subType = contentType.split('/', 1)  # 根据contentType 判断主类型与子类型
        try:
            with open(attachmentFilePath, 'rb') as file:
                if mainType == 'text':  # 根据主类型不同，调用不同的文件读取方法
                    attachment = MIMEBase(mainType, subType)  # A subclass of MIMENonMultipart, the MIMEText class is used to create MIME objects of major type text.
                    attachment.set_payload(file.read())  # Set the entire message object’s payload（负载） to payload.
                    encoders.encode_base64(attachment)     
                    #encode_base64(attachment)  # Encodes the payload into base64 form and sets the Content-Transfer-Encoding header to base64.        
                elif mainType == 'message':  
                    attachment = email.message_from_file(file)  # 使用message_from_file方法，Return a message object structure tree from an open file object
                elif mainType == 'image':  # 图片
                    attachment = MIMEImage(file.read())  #A subclass of MIMENonMultipart, the MIMEImage class is used to create MIME message objects of major type image.
                elif mainType == 'audio':  # 音频
                    attachment = MIMEAudio(file.read(), _subType=subType)  #A subclass of MIMENonMultipart, the MIMEAudio class is used to create MIME message objects of major type audio.
                else:  
                    attachment = MIMEBase(mainType, subType)  # The MIMEBase class always adds a Content-Type header (based on _maintype, _subtype, and _params), and a MIME-Version header (always set to 1.0).
                    attachment.set_payload(file.read())  # Set the entire message object’s payload（负载） to payload.
                    encoders.encode_base64(attachment)
                    # encode_base64(attachment)  
                    # Encodes the payload into base64 form and sets the Content-Transfer-Encoding header to base64.   
                attachment.add_header('Content-Disposition', 'attachment',   filename=os.path.basename(attachmentFilePath))  #Content-Disposition为属性名 disposition-type是以什么方式下载，如attachment为以附件方式下载 disposition-parm为默认保存时的文件名   
        except Exception as e:
            print(e)
            attachment=None
        if attachment is not None:
            msg.attach(attachment)

    def send_ordinary_email(self,smtp_server,port,username, password,sender, receiver, subject, content):
        """
        发送普通邮件的函数。

        参数：
        sender (str): 发件人邮箱地址。
        receiver (str): 收件人邮箱地址，可以是多个，用逗号分隔。
        subject (str): 邮件主题。
        content (str): 邮件正文内容。
        smtp_server (str): SMTP服务器地址。
        port (int): 端口号，默认为465
        username (str): 发件人邮箱的用户名。
        password (str): 发件人邮箱的密码。

        返回值：
        None
        """
        # 创建一个MIMEText对象，用于封装邮件内容
        message = MIMEText(content, 'plain', 'utf-8')
        message['From'] = Header(sender, 'utf-8')  # 发件人
        message['To'] = Header(','.join(receiver), 'utf-8')  # 收件人
        message['Subject'] = Header(subject, 'utf-8')  # 邮件主题

        # 登录SMTP服务器
        try:
            server = smtplib.SMTP_SSL(smtp_server)
            server.login(username, password)
        except smtplib.SMTPException as e:
            print(f"Error: 无法连接到SMTP服务器: {e}")
            return

        # 发送邮件
        try:
            server.sendmail(sender, receiver, message.as_string())
            print("邮件发送成功")
        except smtplib.SMTPException as e:
            print(f"Error: 无法发送邮件: {e}")
        finally:
            # 关闭连接
            server.quit()

    #设置邮件附件
    def set_email_enclosure_base64(self,msg,file):
        txt=None
        try:
            # 读取文件并将文件作为附件添加到邮件消息对象中  
            with open(file, 'rb') as f:
                txt = MIMEText(f.read(), 'base64', 'utf-8')
                #encoded = base64.b64encode(f.read()).decode()
                #取文件名
                file_name=file.split('/')[-1]
                txt.add_header('Content-Disposition','attachment',filename = ('gbk', '', file_name))
        except BaseException as e:
            print(e)
        else:
            msg.attach(txt)
        
    #添加图片附件
    def set_email_enclosure_image(self,msg,file):
        img=None
        try:
            # 读取图片并将其作为附件添加到邮件消息对象中  
            with open(file, 'rb') as f:
                img = MIMEImage(f.read())
                #取文件名
                file_name=file.split('/')[-1]
                img.add_header('Content-Disposition','attachment',filename = ('gbk', '', file_name))
                #img['Content-Disposition'] = 'attachment; filename=image.png'
        except BaseException as e:
            print(e)
        else:
            msg.attach(img)  
        
    #添加pdf附件    
    def set_email_enclosure_pdf(self,msg,file):
        img=None
        try:
            # 读取图片并将其作为附件添加到邮件消息对象中  
            with open(file, 'rb') as f:
                pdf= MIMEApplication(f.read(),_subtype="pdf")
                #取文件名
                file_name=file.split('/')[-1]
                pdf.add_header('Content-Disposition','attachment',filename = ('gbk', '', file_name))
                #img['Content-Disposition'] = 'attachment; filename=image.png'
        except BaseException as e:
            print(e)
        else:
            msg.attach(pdf)
            
#定时发送 port:465
class SchedulerMail(BaseMail):
    def __init__(self):
        pass
    def send_mail_at_time(self,host,port,pwd,title,sender,recevers,content="",type='plain',enclosure=None,send_time=1):
        
        if isinstance(send_time,int):
            #秒数
            delay_seconds=send_time
        elif isinstance(send_time,str):
            #具体时间
            send_time=convert_date_string(send_time)
            if send_time:
                scheduled_time = datetime.strptime(send_time, '%Y/%m/%d %H:%M:%S')
                # 计算当前时间到发送时间的延迟
                delay = scheduled_time - datetime.now()
                # 将延迟转换为秒
                delay_seconds = delay.total_seconds()
            else:
                delay_seconds=1
        else:
            delay_seconds=1
            
        #添加邮件内容
        msg=self.set_attach(title,sender,recevers)
        self.add_body(msg,content,type)
        #附件
        if not enclosure:
            self.add_enclosur(msg,enclosure)
        
        # 创建定时器对象
        scheduler_obj=sched.scheduler(time.time, time.sleep)
        # 添加定时任务
        scheduler_obj.enter(delay_seconds, 1, self.send_email, argument=(host,port,pwd,msg))
        
        if isinstance(send_time,str):
            print(f"邮件将在{send_time}后发送! 请勿退出!")
        else:
            print(f"邮件将在{delay_seconds}秒后发送! 请勿退出!")

        # 启动定时器
        scheduler_obj.run()


def schedule_email(host='smtp.qq.com',port=465,pwd="",sender="sender@qq.com",recevers="receiver@qq.com",subject=None,content="",type='plain',enclosure="",send_time=None):
    """
    定时发送邮件的函数。

    参数：
        host(str): SMTP服务器地址，默认为smtp.qq.com
        port(int): 端口号，默认为465，也可以为25
        pwd(str): 邮箱授权码
        sender (str): 发件人邮箱地址。
        recevers (str): 收件人邮箱地址，可以是多个，用逗号分隔。
        subject (str): 邮件主题。
        content (str): 邮件正文内容。
        type: 邮件内容类型:plain和html
        enclosure: 附件(完整路径)，可以是多个，用逗号分隔，默认None
        send_time: 邮件发送时间，格式:yyy/mm/dd hh:mm:ss
    返回值：
        None
    """
    if not sender or not pwd or not recevers:
        return
    if not type in ("plain", "html"):
        type='plain'
    sm=SchedulerMail()
    sm.send_mail_at_time(host,port,pwd,subject,sender,recevers,content,type,enclosure,send_time)
    
def send_email(host='smtp.qq.com',port=465,pwd="",sender="sender@qq.com",recevers="receiver@qq.com",subject=None,content="",type='plain',enclosure=""):
    """
    发送邮件的函数。

    参数：
        host(str): SMTP服务器地址，默认为smtp.qq.com
        port(int): 端口号，默认为465，也可以为25
        pwd(str): 邮箱授权码
        sender (str): 发件人邮箱地址。
        recevers (str): 收件人邮箱地址，可以是多个，用逗号分隔。
        subject (str): 邮件主题。
        content (str): 邮件正文内容。
        type: 邮件内容类型:plain和html
        enclosure: 附件(完整路径)，可以是多个，用逗号分隔，默认None
    返回值：
        None
    """
    if not sender or not pwd or not recevers:
        return

    if not type in ("plain", "html"):
        type='plain'
    bm=BaseMail()
     #添加邮件内容
    msg=bm.set_attach(subject,sender,recevers)
    bm.add_body(msg,content,type)
    #附件
    if not enclosure:
        bm.add_enclosur(msg,enclosure)
    bm.send_email(host,port,pwd,msg)
    
    
    
def regular_email(host='smtp.qq.com',port=465,user=None,pwd=None,sender=None,recevers="",subject=None,content=""):
    """
    发送邮件的函数。

    参数：
        host(str): SMTP服务器地址，默认为smtp.qq.com
        port(int): 端口号，默认为465，也可以为25
        user (str): 邮箱用户名称
        pwd (str): 邮箱密码
        sender (str): 发件人邮箱地址。
        recevers (str): 收件人邮箱地址，可以是多个，用逗号分隔。
        subject (str): 邮件主题。
        content (str): 邮件正文内容。
    返回值：
        None
    """
    if not user or not pwd or not sender or not recevers:
        return
    bm=BaseMail()
    bm.send_ordinary_email(host, port,user, pwd,sender, recevers, subject, content)
    
    
    
    
    
    
    
    
    
    
    
    

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    